import KoaRouter from 'koa-router'

import 'reflect-metadata'

import { PREFIX_META,METHOD_META,URL_META,PARAMS_META,HTTPSTATUS_META,SETHEADER_META } from '@/decorator/metadata'

import { realType } from '@/util'

interface IControllers{
  controllers: any[]
}
interface IConfigs{
  message?: string
  options?: []
}

class RegisterRouter{
  private app
  private controllers:any[]
  constructor(app,{
    controllers
  }:IControllers){
    this.app = app
    this.controllers = controllers
    this.register()
  }
  register(){
    const router:KoaRouter = new KoaRouter()
    const Controller = this.factory(router)
    this.controllers.forEach(Controller)
  }
  setHeader(ctx,header){
    if(realType(header) == 'object'){
      for(const key in header){
        ctx.set(key,header[key])
      }
    }
  }
  factory(router:KoaRouter){
    return Controller => {
      const factoryController = new Controller()
      const factoryPrototype = Object.getPrototypeOf(factoryController)
      const filter:Function = (method:string | number):boolean => ((method !== 'constructor') && typeof factoryPrototype[method] === 'function')
      const factoryMethods:(string | number)[] = Object.getOwnPropertyNames(factoryPrototype).filter((method:string | number | symbol) => filter(method))
      const path:string | number = Reflect.getMetadata(PREFIX_META,Controller)
      const length:number = factoryMethods.length
      factoryMethods.forEach((factoryMethod:string) => {
        const fn:Function = factoryPrototype[factoryMethod]
        const url:string | number = Reflect.getMetadata(URL_META,fn)
        const method:string = Reflect.getMetadata(METHOD_META,fn)
        const status:number = Reflect.getMetadata(HTTPSTATUS_META,fn)
        const setHeader:object | undefined | null = Reflect.getMetadata(SETHEADER_META,fn)
        const uri:string = path ? `${ path }${ url }` : `${ url }`
        const list:any[] = Reflect.getMetadata(PARAMS_META,factoryController,factoryMethod)
        router[method](uri,async (ctx,next) => {
          ctx.status = status || 200
          this.setHeader(ctx,setHeader)
          try{
            const ops:IConfigs = await this.handler(ctx,next,list,factoryMethod)
            if(ops.message){
              ctx.body = { code: 500,message: ops.message || '' }
            }else{
              const data = await fn.apply(Controller,ops.options)
              ctx.body = { code: 200,data }
            }
          }catch(ex){
            ctx.log.error(ctx,ex,`接口${ uri }请求出错`)
            next()
          }
        })
      })
      this.app.use(router.routes())
      this.app.use(router.allowedMethods())
    }
  }
  params(params,type,method){
    const list:any[] = (params || []).filter(item => item.type == type && item.method == method)
    const result:any[] = Array.isArray(list) ? list : []
    return result
  }
  reqiured(target,base,type){
    const requiredText:string = `${ type }['${ base.name }'] is required`
    const requiredSubText:string = 'but this is not defined'
    let message:string = ''
    console.log(base)
    if(base.required && base.name){
      const value:any = target[base.name]
      const fn:Function | undefined = base.fn
      if(!value){
        message = `${ requiredText }，${ requiredSubText }`
      }else{
        if(realType(fn) === 'function'){
          message = fn(value,message)
        }
      }
    }
    return message
  }
  handler(ctx,next,list,method){
    let array:any = []
    const query = ctx.query
    const body = ctx.request.body
    const params = ctx.params
    const header = ctx.header
    // 获取ctx
    const ctxArgs = this.params(list,'ctx',method)
    ctxArgs.forEach((item,i) => array[ctxArgs[i].index] = ctx)
    // 获取next
    const nextArgs = this.params(list,'next',method)
    nextArgs.forEach((item,i) => array[nextArgs[i].index] = next)
    // 获取query
    const queryArgs = this.params(list,'query',method)
    for(let i=0;i<queryArgs.length;i++){
      const message:string = this.reqiured(query,queryArgs[i],'query')
      if(message)
        return { message }
      array[queryArgs[i].index] = query[queryArgs[i].name]
    }
    // 获取params
    const paramArgs = this.params(list,'params',method)
    for(let i=0;i<paramArgs.length;i++){
      const message:string = this.reqiured(params,paramArgs[i],'params')
      if(message)
        return { message }
      array[paramArgs[i].index] = params[paramArgs[i].name]
    }
    // 获取body
    const bodyArgs = this.params(list,'body',method)
    for(let i=0;i<bodyArgs.length;i++){
      const message:string = this.reqiured(body,bodyArgs[i],'body')
      if(message)
        return { message }
      array[bodyArgs[i].index] = body[bodyArgs[i].name]
    }
    // 获取headers
    const headerArgs = this.params(list,'headers',method)
    for(let i=0;i<headerArgs.length;i++){
      const message:string = this.reqiured(header,headerArgs[i],'headers')
      if(message)
        return { message }
      array[headerArgs[i].index] = header[headerArgs[i].name]
    }
    // session
    const sessionArgs = this.params(list,'session',method)
    for(let i=0;i<sessionArgs.length;i++){
      const session = ctx.session[sessionArgs[i].name]
      const message:string = this.reqiured(ctx.session,sessionArgs[i],'session')
      if(message)
        return { message }
      array[sessionArgs[i].index] = session
    }
    return { options:array }
  }
}

export default RegisterRouter
